#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <cstdlib>

using namespace std;

const char *const TRIANGLE_VERTEX_SHADER[] = {
        "#version 460 core\n"
        "layout (location = 0) in vec3 aPos;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
        "}"
};

const char *const TRIANGLE_FRAGMENT_SHADER[] = {
        "#version 460 core\n"
        "out vec4 FragColor;\n"
        "\n"
        "void main()\n"
        "{\n"
        "    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
        "} "
};

void errorAndExit(const string &str) {
    cerr << str << endl;
    glfwTerminate();
    exit(1);
}

void onWindowSizeChanged(GLFWwindow *, int width, int height) {
    glViewport(0, 0, width, height);
}

void processInput(GLFWwindow *window) {
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
        glfwSetWindowShouldClose(window, true);
    }
}

void checkCompileSuccess(unsigned int shader, size_t logSize, char *logBuffer, int success) {
    if (!success) {
        glGetShaderInfoLog(shader, logSize, nullptr, logBuffer);
        errorAndExit(string("ERROR::SHADER::COMPILATION_FAILED\n")
                     + logBuffer);
    }
}

void checkLinkSuccess(unsigned int program, size_t logSize, char *logBuffer, int success) {
    if (!success) {
        glGetProgramInfoLog(program, logSize, nullptr, logBuffer);
        errorAndExit(string("ERROR::PROGRAM::LINK_FAILED\n")
                     + logBuffer);
    }
}

void initializeShaderProgram(
        unsigned int shaderProgram,
        char *infoLog,
        size_t logSize) {
    int success;
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, TRIANGLE_VERTEX_SHADER, nullptr);
    glCompileShader(vertexShader);
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    checkCompileSuccess(vertexShader, logSize, infoLog, success);
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, TRIANGLE_FRAGMENT_SHADER, nullptr);
    glCompileShader(fragmentShader);
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    checkCompileSuccess(fragmentShader, logSize, infoLog, success);
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
}

int main() {
    float triangles[] = {
            -0.9, -0.5, 0.0,
            0.0, -0.5, 0.0,
            -0.45, 0.5, 0.0,
            0.0, -0.5, 0.0,
            0.9, -0.5, 0.0,
            0.45, 0.5, 0.0
    };
    unsigned int triangleVertexBuffers;
    unsigned int shaderProgram;
    unsigned int vertexArray;
    int compileSuccess;
    char infoLog[512];

    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow *window = glfwCreateWindow(800,
                                          600,
                                          "17343116",
                                          nullptr,
                                          nullptr);
    if (window == nullptr) errorAndExit("Failed to create GLFW window");
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, onWindowSizeChanged);

    int loadResult = gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
    if (!loadResult) errorAndExit("Failed to initialize GLAD");

    glGenVertexArrays(1, &vertexArray);
    glBindVertexArray(vertexArray);

    glGenBuffers(1, &triangleVertexBuffers);
    glBindBuffer(GL_ARRAY_BUFFER, triangleVertexBuffers);
    glBufferData(GL_ARRAY_BUFFER, sizeof(triangles), triangles, GL_STATIC_DRAW);

    shaderProgram = glCreateProgram();
    initializeShaderProgram(shaderProgram, infoLog, sizeof(infoLog));
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &compileSuccess);
    checkLinkSuccess(shaderProgram, sizeof(infoLog), infoLog, compileSuccess);
    glUseProgram(shaderProgram);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), nullptr);
    glEnableVertexAttribArray(0);
    // glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    while (!glfwWindowShouldClose(window)) {
        processInput(window);

        glClearColor(0.2, 0.3, 0.3, 1.0);
        glClear(GL_COLOR_BUFFER_BIT);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}
